 #include <SDL3/SDL.h>
#include "snake_game.h"
#include "snake/unit/snake_food.h"

const int SNAKE_BLOCK_SIZE_IN_PIXELS = 24;

SnakeGame::SnakeGame() : SnakeGame(24, 18, 4) {}

SnakeGame::SnakeGame(int width, int height, int foodCount)
    : width(width), height(height), foodCount(foodCount), unitWidth(SNAKE_BLOCK_SIZE_IN_PIXELS) {
    matrix = new GameUnit*[width * height];
    for (int i = width * height - 1; i >= 0; i--) {
        matrix[i] = nullptr;
    }
    init();
}

int SnakeGame::getWindowsHeight() { return height * unitWidth; }
int SnakeGame::getWindowsWidth() { return width * unitWidth; }

void SnakeGame::resetMatrix() {
    for (int i = width * height - 1; i >= 0; i--) {
        delete matrix[i];
        matrix[i] = nullptr;
    }
}

void SnakeGame::init() {
    resetMatrix();

    Point middle(width / 2, height / 2);
    SnakeCell* head = new SnakeCell(middle);
    snake.reset(head, Direction::RIGHT);
    setCell(middle, head);

    inhibitTailStep = 4;
    
    for (int i = 0; i < foodCount; i++) {
        newFood();
    }
}

SnakeGame::~SnakeGame() {
    resetMatrix();
    delete[] matrix;
}

GameUnit* SnakeGame::getCell(Point& point) {
    if (!isValidPoint(point)) {
        return nullptr;
    }
    return matrix[point.y * width + point.x];
}

void SnakeGame::setCell(Point& point, GameUnit* unit) {
    if (!isValidPoint(point)) {
        return;
    }
    matrix[point.y * width + point.x] = unit;
}

bool SnakeGame::isCellsFull() {
    return snake.size() + foodCount >= width * height;
}

void SnakeGame::newFood() {
    while (true) {
        const int x = (int) SDL_rand(width);
        const int y = (int) SDL_rand(height);
        Point pos(x,y);
        if (getCell(pos) == nullptr) {
            SnakeFood* food = new SnakeFood(pos);
            setCell(pos, food);
            break;
        }
    }
}

void SnakeGame::redir(Direction& dir) {
    Direction oppositeDir = snake.direction.opposite();
    if (dir == oppositeDir) {
        return;
    }
    snake.direction = dir;
}

void SnakeGame::wrapAround(int *val, int max) {
    if (*val < 0) {
        *val = max - 1;
    } else if (*val > max - 1) {
        *val = 0;
    }
}

bool SnakeGame::isValidPoint(Point& point) {
    return point.x >= 0 && point.x < width && point.y >= 0 && point.y < height;
}

Point SnakeGame::getNextPos() {
    SnakeCell* head = snake.getHead();
    Point next = head->getPos().add(snake.direction);
    wrapAround(&next.x, width);
    wrapAround(&next.y, height);
    return next;
}
void SnakeGame::step() {
    if (inhibitTailStep <= 0) {
        removeTail();
    } else {
        inhibitTailStep--;
    }

    Point nextPos = getNextPos();
    GameUnit* unit = getCell(nextPos);
    if (unit == nullptr) {
        moveTo(nextPos);
    } else if (unit->getType() == GameUnitType::Food) {
        delete unit;
        newFood();
        moveTo(nextPos);
        inhibitTailStep++;

        if (isCellsFull()) {
            init();
        }
    } else {
        init();
    }
}

void SnakeGame::removeTail() {
    SnakeCell* tailCell = snake.popTail();
    setCell(tailCell->getPos(), nullptr);
    delete tailCell;
}

void SnakeGame::moveTo(Point& point) {
    SnakeCell* headCell = new SnakeCell(point);
    setCell(point, headCell);
    snake.newCell(headCell);
}

static void set_rect_xy_(SDL_FRect *r, Point& pos)
{
    r->x = (float)(pos.x * SNAKE_BLOCK_SIZE_IN_PIXELS);
    r->y = (float)(pos.y * SNAKE_BLOCK_SIZE_IN_PIXELS);
}
 
void SnakeGame::draw(SDL_Renderer* renderer, ResourceManager& resManager) {
    SDL_FRect r;
    r.w = r.h = SNAKE_BLOCK_SIZE_IN_PIXELS;
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
    SDL_RenderClear(renderer);
    for (int i = 0; i < width; i++) {
        for (int j = 0; j < height; j++) {
        Point p = Point(i, j);
            GameUnit* unit = getCell(p);
            if (unit == nullptr) {
                continue;
            }
            set_rect_xy_(&r, p);
            unit->draw(renderer, &r, resManager);
        }
    }
    SDL_RenderPresent(renderer);
}